package service

import (
	"code.huawei.com/tdc-sdk/mgmt"
	"fmt"
	"github.com/pkg/errors"
	"github.com/spf13/cast"
	"io/ioutil"
	"nft-server/common"
	"nft-server/config"
	"nft-server/dao"
	"nft-server/model"
	"nft-server/util"
	"os"
	"strings"
	"time"
)

/*
* 功能-创建名为collectionIndex的集合
* 参数-collectionIndex，集合名字，必填参数，集合名必须唯一
* 返回值-txid，链上交易编号，err为错误返回对象
 */
func CreateNFTCollection(collectionIndex string) (txId string, err error) {
	txId, err = sdk.CreateNFTCollection(collectionIndex)
	if err != nil {
		return "", errors.Errorf("CreateNFTCollection error: %v", err)
	}
	return txId, nil
}

func SetCollectionName(collectionIndex, name, symbol string) (txID string, err error) {
	txID, err = sdk.SetOption(collectionIndex, name, symbol)
	if err != nil {
		return "", errors.Errorf("SetCollectionName error: %v", err)
	}
	return txID, nil
}

/*
* 工具方法，帮助生成metadata对象，获取对象后开发者可以自定设置属性
* 参数-传入图片字节数组（必须），图片名字，图片存储地址(用obs)，描述信息（用户定义），分类（用户定义）
* 返回值-数字资产的meta信息
 */
func NewImageMetadata(image []byte, name, url, description string, category int) (meta *mgmt.Metadata, err error) {
	//add:图片已存储后无需再次存储
	meta, err = sdk.NewImageMetadata(image, name, url, description, category)
	if err != nil {
		return nil, errors.Errorf("NewImageMetadata error: %v", err)
	}
	return meta, nil
}

//根据业务需要给metadata设置相关属性
func SetMetaDataProperties(properties map[string]string, metadata *mgmt.Metadata) {
	if len(properties) == 0 {
		return
	}
	metadata.Properties = make(map[string]mgmt.Property)
	for propertyKey, propertyValue := range properties {
		oneProperty := mgmt.Property{
			Value: propertyValue,
		}
		metadata.Properties[propertyKey] = oneProperty
	}
}

//从metadata中取出相关属性
func GetPropertiesFromMetaData(metadata *mgmt.Metadata) (properties map[string]string) {
	if metadata == nil {
		return
	}
	properties = make(map[string]string)
	for propertyKey, propertyValue := range metadata.Properties {
		properties[propertyKey] = propertyValue.Value
	}
	return properties
}

//单次铸造,参数检查已在上层调用中检查
func SingleMint(image []byte, name, url, description string, categroy int, collectionIndex, tokenId string, properties map[string]string) (nft *mgmt.NFT, metadata *mgmt.BatchMintData, txid string, err error) {
	tokenURI, errT := sdk.NewImageMetadata(image, name, url, description, categroy)
	if errT != nil {
		return nil, nil, "", errors.Errorf("SingleMint error: %v", errT)
	}
	//增加属性properties map[string]string
	SetMetaDataProperties(properties, tokenURI)

	metadata = new(mgmt.BatchMintData)
	metadata.Amount = cast.ToString(1)
	metadata.StartTokenID = tokenId
	metadata.TokenURI = tokenURI

	if errT != nil {
		return nil, nil, "", errors.Errorf("SingleMint error: %v", errT)
	}
	nft, txid, err = sdk.Mint(collectionIndex, tokenId, tokenURI)
	if err != nil {
		return nil, nil, "", errors.Errorf("SingleMint error: %v", err)
	}

	return nft, metadata, txid, nil
}

//多次铸造,参数检查已在上层调用中检查
func BatchMint(image []byte, name, url, description string, category int, collectionIndex, startTokenID, amount string, properties map[string]string) (nft *mgmt.NFT, metadata *mgmt.BatchMintData, txid string, err error) {
	tokenURI, errT := sdk.NewImageMetadata(image, name, url, description, category)
	if errT != nil {
		return nil, nil, "", errors.Errorf("BatchMint error: %v", errT)
	}
	//增加属性properties map[string]string
	SetMetaDataProperties(properties, tokenURI)
	batchMintData := &mgmt.BatchMintData{StartTokenID: startTokenID, Amount: amount, TokenURI: tokenURI}
	txid, err = sdk.NFT1155.BatchMint(collectionIndex, batchMintData)
	if err != nil {
		return nil, nil, "", errors.Errorf("BatchMint error: %v", err)
	}
	return nft, batchMintData, txid, nil
}

//根据传入的铸造次数参数amount来区分调用的是单次铸造或者多次铸造,参数检查已在上层调用中检查
func Mint(image []byte, name, url, description string, category int, collectionIndex string, startTokenID, amount string, properties map[string]string) (nft *mgmt.NFT, tokenURI *mgmt.BatchMintData, txid string, err error) {
	amountInt := cast.ToInt(amount)
	if amountInt == 1 {
		return SingleMint(image, name, url, description, category, collectionIndex, startTokenID, properties)
	} else if amountInt > 1 && amountInt <= 5000 {
		return BatchMint(image, name, url, description, category, collectionIndex, startTokenID, amount, properties)
	} else {
		return nil, nil, "", errors.Errorf("the amount of one minting is limited between 1 and 5000, and now is %v", amount)
	}
}

/////////////////以下为集合CRUD相关
//判断文件夹是否存在
func PathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

//创建文件夹
func Mkdir(dir string) error {
	exist, err := PathExists(dir)
	if err != nil {
		fmt.Printf("get dir error![%v]\n", err)
		return err
	}
	if exist {
		return nil
	} else {
		//创建文件夹
		err := os.Mkdir(dir, os.ModePerm)
		if err != nil {
			fmt.Printf("mkdir falied[%v]\n", err)
		} else {
			fmt.Printf("mkdir success!\n")
		}
	}
	return err
}
func GetAssetIDWithToken(collectionIndex, tokenId string) string {
	//Concat
	var builder strings.Builder
	builder.WriteString(collectionIndex)
	builder.WriteString(tokenId)
	assetId := builder.String()
	return assetId
}
func GetUrlForSavePicture(collectionIndex, tokenId string) string {
	dir := config.GetConfig().SavePictureConfig.SavePath
	var builder strings.Builder
	builder.WriteString(dir)
	builder.WriteString("/")
	builder.WriteString(collectionIndex)
	builder.WriteString("/")
	builder.WriteString(GetAssetIDWithToken(collectionIndex, tokenId))
	builder.WriteString(".png")
	return builder.String()
}

//图片保存
//暂存到磁盘上
func SavePicture(image []byte, collectionIndex, tokenId string) (url string, err error) {
	dir := config.GetConfig().SavePictureConfig.SavePath
	if err := Mkdir(dir); err != nil {
		return "", errors.Errorf("SavePicture error: %v", err)
	}

	var builder strings.Builder
	builder.WriteString(dir)
	builder.WriteString("/")
	builder.WriteString(collectionIndex)
	if err := Mkdir(builder.String()); err != nil {
		return "", errors.Errorf("SavePicture error: %v", err)
	}

	builder.WriteString("/")
	builder.WriteString(GetAssetIDWithToken(collectionIndex, tokenId))
	builder.WriteString(".png")

	url = builder.String()
	err = ioutil.WriteFile(url, image, 0666)

	if err != nil {
		return url, errors.Errorf("SavePicture error: %v", err)
	}
	return url, nil
}

func SaveFile(file []byte, fileType string) (string, error) {
	fileName := util.CombineTwoString(cast.ToString(time.Now().Unix()), fileType)
	finalKey, err := util.UploadFile(fileName, file)
	if err != nil {
		return "", errors.Errorf("SaveFile error: %v", err)
	}
	return finalKey, nil
}

func UpdateAssetTable(userId int, req common.MintReq, subCollectionIndex, startTokenID int, txId string) error {
	//更新资产关系--批量增加表中资产关系
	err := CreateAssetRelation(req.CollectionIndex, subCollectionIndex, cast.ToString(startTokenID), userId, req.MintAmount)
	if err != nil {
		return err
	}

	//增加asset表中关系
	err = UpdateAssets(req.CollectionIndex, cast.ToString(startTokenID), userId, txId, req.FileUrl, req.Category, req.MintAmount)
	if err != nil {
		return err
	}

	//更新集合tokenId
	//子集合tokenId不更新，无意义
	lock.Lock()
	err = AddTokenId(userId, req.CollectionIndex, util.DefaultSubCollectionIndex, req.MintAmount)
	lock.Unlock()
	if err != nil {
		return err
	}

	//更新集合已铸造数量supplies
	lock.Lock()
	err = UpdateCollectionSupplies(userId, req.CollectionIndex, util.DefaultSubCollectionIndex, req.MintAmount)
	lock.Unlock()
	if err != nil {
		return err
	}
	return nil
}

func CheckAdminFirstMint(collectionIndex string, startTokenID int) error {
	adminUser, err := dao.GetUserDao().GetAdminUser()
	if err != nil {
		err = errors.Errorf("Mint error: %v", err)
	}
	if len(adminUser.Address) == 0 {
		owner, err := NFTOwnerOf(collectionIndex, cast.ToString(startTokenID))
		adminUser.Address = owner
		err = dao.GetUserDao().UpdateUserInfo(adminUser)
		if err != nil {
			return err
		}
	}
	return nil
}

func MintOneSubCollection(mintData common.MintReq, subCollectionIndex, startTokenID int, req common.MintMultipleReq, collection *model.CollectionDetail) error {
	image, _ := util.DownloadFile(mintData.FileUrl)
	//设置图片作者属性--暂时设置
	property := BuildMetaDataPropertiesForMint(mintData.Issuer)
	_, tokenURI, txId, err := Mint(image, mintData.Name, mintData.FileUrl, mintData.Description, mintData.Category, mintData.CollectionIndex, cast.ToString(startTokenID), cast.ToString(mintData.MintAmount), property)
	if err != nil {
		//链上铸造失败恢复状态
		collection.CollectionStatus = util.CollectionUnMint
		_ = UpdateCollectionDetail(collection)
		return err
	}
	//根据总集合创建子集合
	subCollection, err := SaveSubCollectionByMainCollection(collection, subCollectionIndex, mintData)
	if err != nil {
		return err
	}
	//存储metaData
	err = SaveMetaData(req.UserId, subCollection.CollectionIndex, subCollection.SubCollectionIndex, tokenURI)
	if err != nil {
		return err
	}
	//更新资产信息表
	err = UpdateAssetTable(req.UserId, mintData, subCollection.SubCollectionIndex, startTokenID, txId)
	if err != nil {
		return err
	}
	return nil
}
